package
{
	import flash.display.*;
	import flash.errors.IllegalOperationError;
	import flash.geom.Point;
	
	public class Polygon extends MovieClip
	{
		// public vars
		//public var points:Array = null, drawPoints:Array = null;
		private var _type:String;
		private var _drawPoints:Array = null;
		private var _drawSegments:Array = null;
		private var _points:Array = null;
		private var _color:uint;
		private var backgroundShape:Shape = null ;
		
		public function get type():String{return _type;}
		public function set type(t:String):void{_type=t;}
		
		public function get color():uint{return _color;}
		public function set color(c:uint):void {_color=c; redraw();}
		
		public function set drawPoints(dps:Array):void
		{
			_drawPoints = dps;
		}
		public function get drawPoints():Array { return _drawPoints; }
		public function set points(ps:Array):void
		{
			if (drawPoints == null)
			{
				_points = ps;
			}			
		}
		public function get points():Array
		{
			if (drawPoints == null)
			{ 
				return _points;
			} else 
			{
				return DrawPoint.toGeomPoints(this.drawPoints);
			}
		}
		
		/** Do not call from a subclass, may be created directly */
		public function Polygon(points:Array, color:uint, alpha:Number=1)
		{
			if (points != null)
			{
				_type = "polygon";
				_color = color;
				this.points = points;
				drawBackgroundShape(color, alpha);
			}
		}
		public function redraw():void
		{
			if (points != null)
			{
				this.points = points;
				drawBackgroundShape(color, alpha);
			}
		}
		
		public static function Centroid(points:Array):Point{if (points != null){ return MathUtilities.centroid(points);} else { return null; }}
		public static function Area(points:Array):Number{if (points != null){ return MathUtilities.area(points) } else { return Number.NaN; }}
		public static function DiagonalIntersection(points:Array, useCentroidNonQuad:Boolean = true):Point
		{
			if (points != null)
			{
				if (points.length == 4)
				{
					return MathUtilities.intersectionLines(points[0], points[2], points[1], points[3]);
				} else 
				{
					if (useCentroidNonQuad)
					{
						return Centroid(points);
					} else
					{
						return null;
					}
					
				}
			} else
			{
				return null;
			}
		}
		/** Get points centered around centroid */
		public function get centeredPoints():Array
		{
			if (points != null)
			{
				var cpoints:Array = new Array();
				var c:Point = this.centroid;
				for each (var p:Point in points)
				{
					cpoints.push(new Point(p.x-c.x,p.y-c.y));
				}
				return cpoints;
			} else
			{
				return null;
			}
				
		}
		/** Place points with bounding box starting at 0,0 */
		public function get minimizedPoints():Array
		{
			if (points != null)
			{
				var cpoints:Array = new Array();
				var minX:Number=Number.MAX_VALUE , minY:Number=Number.MAX_VALUE;
				for each (var p:Point in points)
				{
					if (p.x < minX) minX = p.x;
					if (p.y < minY) minY = p.y;
				}
				for each (p in points)
				{
					cpoints.push(new Point(p.x-minX,p.y-minY));
				}
				return cpoints;
			} else
			{
				return null;
			}
			
		}
		
		/** Checks to see if this polygon and given polygon intersect.*/
		public function hitTestPolygon (polygon:Polygon, TESTING:Boolean = false):Boolean
		{
			if (this.points != null && polygon.points != null)
			{
				//if (this.x-this.width > polygon.x+polygon.width || this.x+this.width < polygon.x-polygon.width || this.y-this.height > polygon.y+polygon.height || this.y+this.height < polygon.y-polygon.height) return false;
				var p:Point, gp:Point;
				var p_minx:int=int.MAX_VALUE, p_miny:int=int.MAX_VALUE, p_maxx:int=int.MIN_VALUE, p_maxy:int=int.MIN_VALUE, pp_minx:int=int.MAX_VALUE, pp_miny:int=int.MAX_VALUE, pp_maxx:int=int.MIN_VALUE, pp_maxy:int=int.MIN_VALUE;
				var gpoints:Array = new Array(); 
				for each (p in points)
				{
					gp = this.localToGlobal(p); gpoints.push(gp); 
					if (gp.x < p_minx) p_minx = gp.x; if(gp.x > p_maxx) p_maxx = gp.x; if (gp.y < p_miny) p_miny = gp.y; if(gp.y > p_maxy) p_maxy = gp.y;
				}
				//if (TESTING){trace("this points", gpoints)}
				
				var gppoints:Array = new Array(); 
				for each (p in polygon.points)
				{
					gp = polygon.localToGlobal(p); gppoints.push(gp); 
					if(gp.x >= p_minx && gp.x <= p_maxx && gp.y >= p_miny && gp.y <= p_maxy)
					{ 
						if (this.pointWithinBounds(gp)){ return true;}//else{trace ("close miss 1");}
					}
					if (gp.x < pp_minx) pp_minx = gp.x; if(gp.x > pp_maxx) pp_maxx = gp.x; if (gp.y < pp_miny) pp_miny = gp.y; if(gp.y > pp_maxy) pp_maxy = gp.y; 
				}
				//if (TESTING){trace("those points", gppoints)}
				
				for each (gp in gpoints)
				{
					if(gp.x >= pp_minx && gp.x <= pp_maxx && gp.y >= pp_miny && gp.y <= pp_maxy)
					{ 
						if (polygon.pointWithinBounds(gp)){ return true;}//else{trace ("close miss 2");}
					}
				}
				//if (TESTING)
				//{
				//	trace("these", gpoints);
				//	trace("those", gppoints);
				//}
				//var points1WCen:Array = this.points.slice();
				//points1WCen.push(this.centroid);
				
				//var gpoint1WCen:Array; for each (p in points1WCen){gpoint1WCen.push(this
				//var points2WCen:Array = polygon.points.slice();
				//points2WCen.push(polygon.centroid);
				//trace("____________________________________________________");
				//trace(points1WCen);
				//trace(points2WCen);
				//for each (p in gpoints)
				//{
				//	if (polygon.pointWithinBounds(p)){ trace("hit1", p );return true;}
				//}
				
				//for each (p in gppoints)
				//{
				//	if (this.pointWithinBounds(p)){ trace("hit2", p );return true;}
				//}	
				
				return false;
			} else
			{
				return false;
			}
		}

		/** Creates a temporary drawn shape, connecting all points, and tests whether input point is within the shapes bounds */
		public function pointWithinBounds(gpoint:Point, useRayCasting:Boolean=true):Boolean
		{
			
			if (useRayCasting)
			{
				var icount:uint = this.intersectionCountFromPoint(gpoint);
				
				if (icount%2==1)
				{ 
					//trace(icount, "intersections", gpoint); 
					return true; 
				}
				else { return false; }	
			} else
			{
				return this.hitTestPoint(gpoint.x, gpoint.y, true);
			}
		}
		
		/** How many intersections are there from a point to an off-screen point, along given direction (radians)?
		 * For use in point-in-polygon problem */
		public function intersectionCountFromPoint(point:Point, radians:Number=Math.PI/2):uint
		{
			// get second point off-screen
			var swidth:Number, sheight:Number;
			if (this.parent !=null){swidth=this.stage.stageWidth; sheight=this.stage.stageWidth;}
			else {swidth=1000; sheight=700;}
			var dist:Number = Math.sqrt(swidth*swidth+sheight*sheight);
			var pointoff:Point = new Point(point.x+dist*Math.sin(radians), point.y-dist*Math.cos(radians));
			var count:uint = 0;
			// assume points are ordered
			//trace(ParentPolygon(this).id, "check polygon -----------------------------");
			for (var i:int=0; i < points.length; i++)
			{
				if (points[i] != null && points[(i+1)%points.length] != null)
				{
					var p1:Point = this.localToGlobal(points[i]);
					var p2:Point = this.localToGlobal(points[(i+1)%points.length]);
					//trace(p1, p2, point, pointoff);
					if (MathUtilities.intersectionSegments(point, pointoff,p1, p2) != null) count++;
				}
			}
			return count;
		}
		
		public function get centroid():Point{if (points != null){ return MathUtilities.centroid(points);} else { return null; }}
		public function get area():Number{if (points != null){ return MathUtilities.area(points) } else { return Number.NaN; }}
		public function get diagonalIntersection():Point
		{
			if (points != null)
			{
				if (points.length == 4)
				{
					return MathUtilities.intersectionLines(points[0], points[2], points[1], points[3]);
				} else 
				{
					return null;
				}
			} else
			{
				return null;
			}
		}
		/** Is the input draw point contained on this polygon */
		public function containsDrawPoint(dp:DrawPoint, byRefOnly:Boolean=true):Boolean
		{
			if (this.drawPoints != null)
			{
				for each (var _dp:DrawPoint in drawPoints)
				{
					if (dp == _dp || (!byRefOnly && _dp.x == dp.x && _dp.y == dp.y)) return true;
				}
			}
			return false;
		}
		/** Is the input draw segment contained on this polygon */
		public function containsDrawSegment(ds:DrawSegment):Boolean
		{
			var drawSegments:Array = this.orderedDrawSegments; 
			if (drawSegments != null)
			{
				for each (var _ds:DrawSegment in drawSegments)
				{
					if (_ds == ds) return true;
				}
			}
			return false;
		}
		/** Is the input draw angle contained on this polygon */
		public function containsDrawAngle(da:DrawAngle):Boolean
		{
			if (this.drawPoints != null)
			{
				for each (var _dp:DrawPoint in drawPoints)
				{
					for each (var _da:DrawAngle in _dp.drawAngles)
					{
						if (_da == da) return true;
					}
				}
			}
			return false;
		}
		/** Is the input point contained within this polygon? */
		public function containsPoint(_ip:Point, byRefOnly:Boolean=false):Boolean
		{
			if (points != null)
			{
				for each (var _p:Point in points)
				{
					if (_p == _ip || (!byRefOnly && _ip.x == _p.x && _ip.y == _p.y)) return true;
				}
			}
			return false;
		}
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsSupersetOfDrawPoints(idpoints:Array):Boolean
		{
			if (drawPoints != null)
			{
				for each (var _ip:DrawPoint in idpoints)
				{
					var found:Boolean = false;
					for each (var _p:DrawPoint in drawPoints)
					{
						if (_ip == _p )
						{
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				return true;
			}
			return false;
		}
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsSubsetOfDrawPoints(idpoints:Array):Boolean
		{
			if (drawPoints != null)
			{
				// match all 
				for each (var _p:DrawPoint in drawPoints)
				{
					var found:Boolean = false;
					for each (var _ip:DrawPoint in idpoints)
					{
						if (_ip == _p )
						{
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				return true;
			}
			return false;
		}
		
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsEquivalentDrawPoints(idpoints:Array):Boolean
		{
			if (this.containsSupersetOfDrawPoints(idpoints) && this.containsSubsetOfDrawPoints(idpoints))
			{
				return true;				
			}else
			{
				return false;
			}
		}
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsSupersetOfPoints(ipoints:Array, byRefOnly:Boolean=false):Boolean
		{
			if (points != null)
			{
				for each (var _ip:Point in ipoints)
				{
					var found:Boolean = false;
					for each (var _p:Point in points)
					{
						if (_ip == _p || (!byRefOnly && _ip.x == _p.x && _ip.y == _p.y))
						{
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				return true;
			}
			return false;
		}
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsSubsetOfPoints(ipoints:Array, byRefOnly:Boolean=false):Boolean
		{
			if (points != null)
			{
				// match all 
				for each (var _p:Point in points)
				{
					var found:Boolean = false;
					for each (var _ip:Point in ipoints)
					{
						if (_ip == _p || (!byRefOnly && _ip.x == _p.x && _ip.y == _p.y))
						{
							found = true;
							break;
						}
					}
					if (!found) return false;
				}
				return true;
			}
			return false;
		}
		
		/** Are the input points a subset (or equivalent set) to points in this polygon */
		public function containsEquivalentPoints(ipoints:Array, byRefOnly:Boolean=true):Boolean
		{
			if (this.containsSupersetOfPoints(ipoints, byRefOnly) && this.containsSubsetOfPoints(ipoints, byRefOnly))
			{
				return true;				
			}else
			{
				return false;
			}
		}
		
		/** Returns an array of draw segments that start from a single point and will return to that point */
		public function get orderedDrawSegments():Array
		{
			if (drawPoints != null)
			{
				var prevds:Array = new Array();
				var rootdp:DrawPoint = drawPoints[0];
				var ds:DrawSegment
				var dp1:DrawPoint, dp2:DrawPoint;
				
				for (var i:int=0; i < drawPoints.length-1; i++)
				{
					dp1 = drawPoints[i]; dp2 = drawPoints[i+1];
					for each (ds in dp1.drawSegments)
					{
						if (ds.point1==dp2 || ds.point2==dp2){prevds.push(ds); break;}
					}
				}
				// get segment that leads back to root
				dp1 = drawPoints[drawPoints.length-1];
				for each (ds in dp1.drawSegments)
				{
					if (ds.point1==rootdp || ds.point2==rootdp){prevds.push(ds); break;}
				}
				return prevds;
			} else 
			{
				return null;
			}
		}
		
		/** Checks to see if any two DrawSegments intersect each other (besides adjacent) */
		public function containsIntersections():Boolean
		{
			var prevds:Array = this.orderedDrawSegments;
			if (prevds != null && prevds.length >= 3)
			{
				var j:int;
				var ds:DrawSegment, _ds:DrawSegment;
				for (var i:int=2; i < prevds.length; i++)
				{
					ds = prevds[i];	
					// compare to all prior except adjacent, and first
					if (i == prevds.length-1)
					{
						for (j=1; j < i-1; j++)
						{
							_ds = prevds[j];
							//trace("hittest", ds.id, _ds.id);
							if (ds.intersectsDrawSegment(_ds)) return true;
							//if (ds.hitTestObject(_ds)) return true;
						}
					} else
					{
						for (j=0; j < i-1; j++)
						{
							_ds = prevds[j];
							//trace("hittest", ds.id, _ds.id);
							if (ds.intersectsDrawSegment(_ds)) return true;
							//if (ds.hitTestObject(_ds)) return true;
						}
					}
				}
			}
			return false;
		}
		
		private function drawBackgroundShape(color:uint, alpha:uint=1):void
		{
			
			if (backgroundShape == null)
			{
				backgroundShape = new Shape();
				this.addChildAt(backgroundShape, 0);
			} else
			{
				backgroundShape.graphics.clear();
			}
			var tpoints:Array = points;
			if (tpoints != null && tpoints.length > 2)
			{
				backgroundShape.graphics.moveTo(tpoints[tpoints.length-1].x, tpoints[tpoints.length-1].y);
				backgroundShape.graphics.beginFill(color, alpha);
				for each (var p:Point in tpoints)
				{
					backgroundShape.graphics.lineTo(p.x, p.y);				
				}
				backgroundShape.graphics.endFill();
			}
			
		}
	}
}